OneToOneOwnedByTargetConfigurer.java
package org.codefilarete.stalactite.engine.configurer.onetoone;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.codefilarete.reflection.AccessorByMethodReference;
import org.codefilarete.reflection.AccessorDefinition;
import org.codefilarete.reflection.Accessors;
import org.codefilarete.reflection.Mutator;
import org.codefilarete.reflection.ValueAccessPoint;
import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.JoinColumnNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.UniqueConstraintNamingStrategy;
import org.codefilarete.stalactite.dsl.property.CascadeOptions.RelationMode;
import org.codefilarete.stalactite.engine.runtime.ConfiguredPersister;
import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree;
import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.JoinType;
import org.codefilarete.stalactite.engine.runtime.load.PassiveJoinNode;
import org.codefilarete.stalactite.engine.runtime.load.RelationJoinNode;
import org.codefilarete.stalactite.engine.runtime.onetoone.OneToOneOwnedByTargetEngine;
import org.codefilarete.stalactite.mapping.EntityMapping;
import org.codefilarete.stalactite.sql.ConnectionConfiguration;
import org.codefilarete.stalactite.sql.Dialect;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Key;
import org.codefilarete.stalactite.sql.ddl.structure.Key.KeyBuilder;
import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.result.BeanRelationFixer;
import org.codefilarete.tool.Duo;
/**
* @param <SRC> type of input (left/source entities)
* @param <TRGT> type of output (right/target entities)
* @param <SRCID> identifier type of source entities
* @param <TRGTID> identifier type of target entities
* @param <LEFTTABLE> left (source entity) table type
* @param <RIGHTTABLE> right (target entity) table type
* @author Guillaume Mary
*/
public class OneToOneOwnedByTargetConfigurer<SRC, TRGT, SRCID, TRGTID, LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>>
extends OneToOneConfigurerTemplate<SRC, TRGT, SRCID, TRGTID, LEFTTABLE, RIGHTTABLE, SRCID> {
private final Map<Column<LEFTTABLE, ?>, Column<RIGHTTABLE, ?>> keyColumnsMapping = new HashMap<>();
private final JoinColumnNamingStrategy joinColumnNamingStrategy;
private final ForeignKeyNamingStrategy foreignKeyNamingStrategy;
private final Dialect dialect;
private final ConnectionConfiguration connectionConfiguration;
private OneToOneOwnedByTargetEngine<SRC, TRGT, SRCID, TRGTID, LEFTTABLE, RIGHTTABLE> engine;
/**
* Right table column for join, may be right table primary key or column pointing to left table primary key.
* Stored as attribute because it is used out of case designed by parent class : for foreign key maintenance in
* read-only case.
*/
private Key<RIGHTTABLE, SRCID> rightKey;
public OneToOneOwnedByTargetConfigurer(ConfiguredRelationalPersister<SRC, SRCID> sourcePersister,
OneToOneRelation<SRC, TRGT, TRGTID> oneToOneRelation,
JoinColumnNamingStrategy joinColumnNamingStrategy,
ForeignKeyNamingStrategy foreignKeyNamingStrategy,
UniqueConstraintNamingStrategy uniqueConstraintNamingStrategy,
Dialect dialect,
ConnectionConfiguration connectionConfiguration) {
super(sourcePersister, oneToOneRelation, uniqueConstraintNamingStrategy);
this.joinColumnNamingStrategy = joinColumnNamingStrategy;
this.foreignKeyNamingStrategy = foreignKeyNamingStrategy;
this.dialect = dialect;
this.connectionConfiguration = connectionConfiguration;
}
@Override
protected Duo<Key<LEFTTABLE, SRCID>, Key<RIGHTTABLE, SRCID>> determineForeignKeyColumns(EntityMapping<SRC, SRCID, LEFTTABLE> mappingStrategy,
EntityMapping<TRGT, TRGTID, RIGHTTABLE> targetMappingStrategy) {
// left column is always left table primary key
Key<LEFTTABLE, SRCID> leftKey = mappingStrategy.getTargetTable().getPrimaryKey();
// gathering reverse column
Column<RIGHTTABLE, SRCID> reverseColumn = oneToOneRelation.getReverseColumn();
if (reverseColumn == null) {
String reverseColumnName = oneToOneRelation.getReverseColumnName();
if (reverseColumnName != null) {
reverseColumn = targetMappingStrategy.getTargetTable().addColumn(reverseColumnName, mappingStrategy.getIdMapping().getIdentifierInsertionManager().getIdentifierType());
}
}
// right column depends on relation owner
if (reverseColumn != null) {
rightKey = Key.ofSingleColumn(reverseColumn);
PrimaryKey<LEFTTABLE, SRCID> sourcePrimaryKey = sourcePersister.<LEFTTABLE>getMainTable().getPrimaryKey();
if (sourcePrimaryKey.isComposed()) {
throw new UnsupportedOperationException("Can't map composite primary key " + sourcePrimaryKey + " on single reverse foreign key : " + reverseColumn);
} else {
keyColumnsMapping.put(sourcePrimaryKey.getColumns().getAt(0), reverseColumn);
}
}
if (oneToOneRelation.getReverseGetter() != null) {
AccessorByMethodReference<TRGT, SRC> localReverseGetter = Accessors.accessorByMethodReference(oneToOneRelation.getReverseGetter());
AccessorDefinition accessorDefinition = AccessorDefinition.giveDefinition(localReverseGetter);
// we add a column for reverse mapping if one is not already declared
rightKey = createOrUseReverseColumn(targetMappingStrategy, reverseColumn, localReverseGetter, accessorDefinition);
} else if (oneToOneRelation.getReverseSetter() != null) {
ValueAccessPoint<TRGT> reverseSetter = Accessors.mutatorByMethodReference(oneToOneRelation.getReverseSetter());
AccessorDefinition accessorDefinition = AccessorDefinition.giveDefinition(reverseSetter);
// we add a column for reverse mapping if one is not already declared
rightKey = createOrUseReverseColumn(targetMappingStrategy, reverseColumn, reverseSetter, accessorDefinition);
}
// According to the nullable option, we specify the ddl schema option
rightKey.getColumns().forEach(c -> ((Column) c).nullable(oneToOneRelation.isNullable()));
// adding foreign key constraint
// we don't create foreign key for table-per-class because source columns should reference different tables (the one
// per entity) which databases do not allow
boolean createForeignKey = !oneToOneRelation.isTargetTablePerClassPolymorphic();
if (createForeignKey) {
String foreignKeyName = foreignKeyNamingStrategy.giveName(rightKey, leftKey);
// Note that rightColumn can't be null because RelationOwnedByTargetConfigurer is used when one of cascadeOne.getReverseColumn(),
// or cascadeOne.giveReverseSetter() is not null
((Table) rightKey.getTable()).addForeignKey(foreignKeyName, rightKey, leftKey);
}
return new Duo<>(leftKey, rightKey);
}
@Override
protected void addUniqueConstraint(Column<?, ?> column) {
ValueAccessPoint<?> propertyAccessor;
if (oneToOneRelation.getReverseGetter() != null) {
propertyAccessor = Accessors.accessorByMethodReference(oneToOneRelation.getReverseGetter());
} else if (oneToOneRelation.getReverseSetter() != null) {
propertyAccessor = Accessors.mutatorByMethodReference(oneToOneRelation.getReverseSetter());
} else {
propertyAccessor = oneToOneRelation.getTargetProvider();
}
String constraintName = uniqueConstraintNamingStrategy.giveName(propertyAccessor, column);
column.getTable().addUniqueConstraint(constraintName, column);
}
private Key<RIGHTTABLE, SRCID> createOrUseReverseColumn(
EntityMapping<TRGT, TRGTID, RIGHTTABLE> targetMappingStrategy,
Column<RIGHTTABLE, SRCID> reverseColumn,
ValueAccessPoint<TRGT> reverseGetter,
AccessorDefinition accessorDefinition) {
if (reverseColumn == null) {
// no reverse column was given, so we look for the one mapped under the reverse getter
reverseColumn = (Column<RIGHTTABLE, SRCID>) targetMappingStrategy.getPropertyToColumn().get(reverseGetter);
if (reverseColumn == null) {
// no column is defined under reverse getter, then we have to create one
PrimaryKey<LEFTTABLE, SRCID> sourcePrimaryKey = sourcePersister.<LEFTTABLE>getMainTable().getPrimaryKey();
KeyBuilder<RIGHTTABLE, SRCID> result = Key.from(targetMappingStrategy.getTargetTable());
sourcePrimaryKey.getColumns().forEach(pkColumn -> {
Column<RIGHTTABLE, ?> column = targetMappingStrategy.getTargetTable().addColumn(
joinColumnNamingStrategy.giveName(accessorDefinition, pkColumn),
pkColumn.getJavaType());
keyColumnsMapping.put(pkColumn, column);
result.addColumn(column);
});
return result.build();
}
}
return Key.ofSingleColumn(reverseColumn);
}
@Override
protected void addWriteCascades(ConfiguredPersister<TRGT, TRGTID> targetPersister) {
this.engine = new OneToOneOwnedByTargetEngine<>(sourcePersister, targetPersister, oneToOneRelation.getTargetProvider(), keyColumnsMapping);
boolean writeAuthorized = oneToOneRelation.getRelationMode() != RelationMode.READ_ONLY;
if (writeAuthorized) {
super.addWriteCascades(targetPersister);
} else {
this.engine.addForeignKeyMaintainer(dialect, connectionConfiguration, rightKey);
}
}
@Override
protected BeanRelationFixer<SRC, TRGT> determineRelationFixer() {
Mutator<SRC, TRGT> sourceIntoTargetFixer = oneToOneRelation.getTargetProvider().toMutator();
BeanRelationFixer<SRC, TRGT> result;
if (oneToOneRelation.getReverseGetter() != null) {
AccessorByMethodReference<TRGT, SRC> localReverseGetter = Accessors.accessorByMethodReference(oneToOneRelation.getReverseGetter());
AccessorDefinition accessorDefinition = AccessorDefinition.giveDefinition(localReverseGetter);
// we take advantage of foreign key computing and presence of AccessorDefinition to build relation fixer which is needed lately in determineRelationFixer(..)
Mutator<TRGT, SRC> targetIntoSourceFixer = Accessors.mutatorByMethod(accessorDefinition.getDeclaringClass(), accessorDefinition.getName());
result = (src, target) -> {
// fixing source on target
if (target != null) { // prevent NullPointerException, actually means no linked entity (null relation), so nothing to do
targetIntoSourceFixer.set(target, src);
}
// fixing target on source
sourceIntoTargetFixer.set(src, target);
};
} else if (oneToOneRelation.getReverseSetter() != null) {
// we take advantage of foreign key computing and presence of AccessorDefinition to build relation fixer which is needed lately in determineRelationFixer(..)
result = (target, input) -> {
// fixing target on source side
oneToOneRelation.getReverseSetter().accept(input, target);
// fixing source on target side
sourceIntoTargetFixer.set(target, input);
};
} else {
// non bidirectional relation : relation is owned by target without defining any way to fix it in memory
// we can only fix target on source side
result = sourceIntoTargetFixer::set;
}
return result;
}
@Override
protected void addSelectIn2Phases(
String tableAlias,
ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister,
Key<LEFTTABLE, SRCID> leftKey,
Key<RIGHTTABLE, SRCID> rightKey,
FirstPhaseCycleLoadListener<SRC, TRGTID> firstPhaseCycleLoadListener) {
RIGHTTABLE targetTable = targetPersister.<RIGHTTABLE>getMapping().getTargetTable();
RIGHTTABLE targetTableClone = (RIGHTTABLE) new Table(targetTable.getName());
KeyBuilder<RIGHTTABLE, SRCID> relationOwnerForeignKey = Key.from(targetTableClone);
((Set<Column<RIGHTTABLE, ?>>) (Set) rightKey.getColumns()).forEach(column ->
relationOwnerForeignKey.addColumn(targetTableClone.addColumn(column.getName(), column.getJavaType()))
);
KeyBuilder<RIGHTTABLE, TRGTID> relationOwnerPrimaryKey = Key.from(targetTableClone);
targetTable.getPrimaryKey().getColumns().forEach(column ->
relationOwnerPrimaryKey.addColumn(targetTableClone.addColumn(column.getName(), column.getJavaType()))
);
String joinName = sourcePersister.getEntityJoinTree().addPassiveJoin(
EntityJoinTree.ROOT_JOIN_NAME,
leftKey,
relationOwnerForeignKey.build(),
tableAlias,
oneToOneRelation.isNullable() ? JoinType.OUTER : JoinType.INNER,
(Set<Column<RIGHTTABLE, ?>>) (Set) relationOwnerPrimaryKey.build().getColumns(),
(nodeEntity, columnValueProvider) -> {
TRGTID trgtId = targetPersister.getMapping().getIdMapping().getIdentifierAssembler().assemble(columnValueProvider);
firstPhaseCycleLoadListener.onFirstPhaseRowRead(nodeEntity, trgtId);
},
true);
// Propagating 2-phases load to all nodes that use cycling type
PassiveJoinNode passiveJoin = (PassiveJoinNode) sourcePersister.getEntityJoinTree().getJoin(joinName);
targetPersister.getEntityJoinTree().foreachJoin(joinNode -> {
if (joinNode instanceof RelationJoinNode
&& ((RelationJoinNode<?, ?, ?, ?, ?>) joinNode).getEntityInflater().getEntityType() == sourcePersister.getClassToPersist()) {
EntityJoinTree.cloneNodeForParent(passiveJoin, joinNode, leftKey);
}
});
}
@Override
protected void addInsertCascade(ConfiguredPersister<TRGT, TRGTID> targetPersister) {
super.addInsertCascade(targetPersister);
engine.addInsertCascade();
}
@Override
protected void addUpdateCascade(ConfiguredPersister<TRGT, TRGTID> targetPersister, boolean orphanRemoval) {
super.addUpdateCascade(targetPersister, orphanRemoval);
engine.addUpdateCascade(orphanRemoval);
}
@Override
protected void addDeleteCascade(ConfiguredPersister<TRGT, TRGTID> targetPersister, boolean orphanRemoval) {
engine.addDeleteCascade(orphanRemoval);
}
}